<?php
// 应用公共文件

use app\admin\model\Config as configModel;
use app\common\model\banxi\core\CoreAccountClassHour as CoreAccountClassHourModel;
use ba\Filesystem;
use ba\Random;
use Symfony\Component\HttpFoundation\IpUtils;
use think\App;
use think\exception\HttpResponseException;
use think\facade\Config;
use think\facade\Db;
use think\facade\Event;
use think\facade\Lang;
use think\Response;
use voku\helper\AntiXSS;

if (!function_exists('__')) {

    /**
     * 语言翻译
     * @param string $name 被翻译字符
     * @param array $vars 替换字符数组
     * @param string $lang 翻译语言
     * @return mixed
     */
    function __(string $name, array $vars = [], string $lang = ''): mixed
    {
        if (is_numeric($name) || !$name) {
            return $name;
        }
        return Lang::get($name, $vars, $lang);
    }
}

if (!function_exists('filter')) {

    /**
     * 输入过滤
     * 富文本反XSS请使用 clean_xss，也就不需要及不能再 filter 了
     * @param string $string 要过滤的字符串
     * @return string
     */
    function filter(string $string): string
    {
        // 去除字符串两端空格（对防代码注入有一定作用）
        $string = trim($string);

        // 过滤html和php标签
        $string = strip_tags($string);

        // 特殊字符转实体
        return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
    }
}

if (!function_exists('clean_xss')) {

    /**
     * 清理XSS
     * 通常只用于富文本，比 filter 慢
     * @param string $string
     * @return string
     */
    function clean_xss(string $string): string
    {
        $antiXss = new AntiXSS();

        // 允许 style 属性（style="list-style-image: url(javascript:alert(0))" 任然可被正确过滤）
        $antiXss->removeEvilAttributes(['style']);

        // 检查到 xss 代码之后使用 cleanXss 替换它
        $antiXss->setReplacement('cleanXss');

        return $antiXss->xss_clean($string);
    }
}

if (!function_exists('htmlspecialchars_decode_improve')) {
    /**
     * html解码增强
     * 被 filter函数 内的 htmlspecialchars 编码的字符串，需要用此函数才能完全解码
     * @param string $string
     * @param int $flags
     * @return string
     */
    function htmlspecialchars_decode_improve(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string
    {
        return htmlspecialchars_decode($string, $flags);
    }
}

if (!function_exists('get_sys_config')) {

    /**
     * 获取站点的系统配置，不传递参数则获取所有配置项
     * @param string $name 变量名
     * @param string $group 变量分组，传递此参数来获取某个分组的所有配置项
     * @param bool $concise 是否开启简洁模式，简洁模式下，获取多项配置时只返回配置的键值对
     * @return mixed
     * @throws Throwable
     */
    function get_sys_config(string $name = '', string $group = '', bool $concise = true): mixed
    {
        if ($name) {
            // 直接使用->value('value')不能使用到模型的类型格式化
            $config = configModel::cache($name, null, configModel::$cacheTag)->where('name', $name)->find();
            if ($config) $config = $config['value'];
        } else {
            if ($group) {
                $temp = configModel::cache('group' . $group, null, configModel::$cacheTag)->where('group', $group)->select()->toArray();
            } else {
                $temp = configModel::cache('sys_config_all', null, configModel::$cacheTag)->order('weigh desc')->select()->toArray();
            }
            if ($concise) {
                $config = [];
                foreach ($temp as $item) {
                    $config[$item['name']] = $item['value'];
                }
            } else {
                $config = $temp;
            }
        }
        return $config;
    }
}

if (!function_exists('get_route_remark')) {

    /**
     * 获取当前路由后台菜单规则的备注信息
     * @return string
     */
    function get_route_remark(): string
    {
        $controllerName = request()->controller(true);
        $actionName = request()->action(true);
        $path = str_replace('.', '/', $controllerName);

        $remark = Db::name('admin_rule')
            ->where('name', $path)
            ->whereOr('name', $path . '/' . $actionName)
            ->value('remark');
        return __((string)$remark);
    }
}

if (!function_exists('full_url')) {

    /**
     * 获取资源完整url地址；若安装了云存储或 config/buildadmin.php 配置了CdnUrl，则自动使用对应的CdnUrl
     * @param string $relativeUrl 资源相对地址 不传入则获取域名
     * @param string|bool $domain 是否携带域名 或者直接传入域名
     * @param string $default 默认值
     * @return string
     */
    function full_url(string $relativeUrl = '', string|bool $domain = true, string $default = ''): string
    {
        // 存储/上传资料配置
        Event::trigger('uploadConfigInit', App::getInstance());

        $cdnUrl = Config::get('buildadmin.cdn_url');
        if (!$cdnUrl) {
            $cdnUrl = request()->upload['cdn'] ?? '//' . request()->host();
        }

        if ($domain === true) {
            $domain = $cdnUrl;
        } elseif ($domain === false) {
            $domain = '';
        }

        $relativeUrl = $relativeUrl ?: $default;
        if (!$relativeUrl) return $domain;

        $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
        if (preg_match('/^http(s)?:\/\//', $relativeUrl) || preg_match($regex, $relativeUrl) || $domain === false) {
            return $relativeUrl;
        }

        $url = $domain . $relativeUrl;
        $cdnUrlParams = Config::get('buildadmin.cdn_url_params');
        if ($domain === $cdnUrl && $cdnUrlParams) {
            $separator = str_contains($url, '?') ? '&' : '?';
            $url .= $separator . $cdnUrlParams;
        }

        return $url;
    }
}

if (!function_exists('encrypt_password')) {

    /**
     * 加密密码
     * @deprecated 使用 hash_password 代替
     */
    function encrypt_password($password, $salt = '', $encrypt = 'md5')
    {
        return $encrypt($encrypt($password) . $salt);
    }
}

if (!function_exists('hash_password')) {

    /**
     * 创建密码散列（hash）
     */
    function hash_password(string $password): string
    {
        return password_hash($password, PASSWORD_DEFAULT);
    }
}

if (!function_exists('verify_password')) {

    /**
     * 验证密码是否和散列值匹配
     * @param string $password 密码
     * @param string $hash 散列值
     * @param array $extend 扩展数据
     */
    function verify_password(string $password, string $hash, array $extend = []): bool
    {
        // 第一个表达式直接检查是否为 password_hash 函数创建的 hash 的典型格式，即：$algo$cost$salt.hash
        if (str_starts_with($hash, '$') || password_get_info($hash)['algoName'] != 'unknown') {
            return password_verify($password, $hash);
        } else {
            // 兼容旧版 md5 加密的密码
            return encrypt_password($password, $extend['salt'] ?? '') === $hash;
        }
    }
}

if (!function_exists('str_attr_to_array')) {

    /**
     * 将字符串属性列表转为数组
     * @param string $attr 属性，一行一个，无需引号，比如：class=input-class
     * @return array
     */
    function str_attr_to_array(string $attr): array
    {
        if (!$attr) return [];
        $attr = explode("\n", trim(str_replace("\r\n", "\n", $attr)));
        $attrTemp = [];
        foreach ($attr as $item) {
            $item = explode('=', $item);
            if (isset($item[0]) && isset($item[1])) {
                $attrVal = $item[1];
                if ($item[1] === 'false' || $item[1] === 'true') {
                    $attrVal = !($item[1] === 'false');
                } elseif (is_numeric($item[1])) {
                    $attrVal = (float)$item[1];
                }
                if (strpos($item[0], '.')) {
                    $attrKey = explode('.', $item[0]);
                    if (isset($attrKey[0]) && isset($attrKey[1])) {
                        $attrTemp[$attrKey[0]][$attrKey[1]] = $attrVal;
                        continue;
                    }
                }
                $attrTemp[$item[0]] = $attrVal;
            }
        }
        return $attrTemp;
    }
}

if (!function_exists('action_in_arr')) {

    /**
     * 检测一个方法是否在传递的数组内
     * @param array $arr
     * @return bool
     */
    function action_in_arr(array $arr = []): bool
    {
        $arr = is_array($arr) ? $arr : explode(',', $arr);
        if (!$arr) {
            return false;
        }
        $arr = array_map('strtolower', $arr);
        if (in_array(strtolower(request()->action()), $arr) || in_array('*', $arr)) {
            return true;
        }
        return false;
    }
}

if (!function_exists('build_suffix_svg')) {

    /**
     * 构建文件后缀的svg图片
     * @param string $suffix 文件后缀
     * @param ?string $background 背景颜色，如：rgb(255,255,255)
     * @return string
     */
    function build_suffix_svg(string $suffix = 'file', string $background = null): string
    {
        $suffix = mb_substr(strtoupper($suffix), 0, 4);
        $total = unpack('L', hash('adler32', $suffix, true))[1];
        $hue = $total % 360;
        [$r, $g, $b] = hsv2rgb($hue / 360, 0.3, 0.9);

        $background = $background ?: "rgb($r,$g,$b)";

        return '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
            <path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
            <path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
            <polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
            <path style="fill:' . $background . ';" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
            <path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
            <g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">' . $suffix . '</tspan></text></g>
        </svg>';
    }
}

if (!function_exists('get_area')) {

    /**
     * 获取省份地区数据
     * @throws Throwable
     */
    function get_area(): array
    {
        $province = request()->get('province', '');
        $city = request()->get('city', '');
        $where = ['pid' => 0, 'level' => 1];
        if ($province !== '') {
            $where['pid'] = $province;
            $where['level'] = 2;
            if ($city !== '') {
                $where['pid'] = $city;
                $where['level'] = 3;
            }
        }
        return Db::name('area')
            ->where($where)
            ->field('id as value,name as label')
            ->select()
            ->toArray();
    }
}

if (!function_exists('hsv2rgb')) {
    function hsv2rgb($h, $s, $v): array
    {
        $r = $g = $b = 0;

        $i = floor($h * 6);
        $f = $h * 6 - $i;
        $p = $v * (1 - $s);
        $q = $v * (1 - $f * $s);
        $t = $v * (1 - (1 - $f) * $s);

        switch ($i % 6) {
            case 0:
                $r = $v;
                $g = $t;
                $b = $p;
                break;
            case 1:
                $r = $q;
                $g = $v;
                $b = $p;
                break;
            case 2:
                $r = $p;
                $g = $v;
                $b = $t;
                break;
            case 3:
                $r = $p;
                $g = $q;
                $b = $v;
                break;
            case 4:
                $r = $t;
                $g = $p;
                $b = $v;
                break;
            case 5:
                $r = $v;
                $g = $p;
                $b = $q;
                break;
        }

        return [
            floor($r * 255),
            floor($g * 255),
            floor($b * 255)
        ];
    }
}

if (!function_exists('ip_check')) {

    /**
     * IP检查
     * @throws Throwable
     */
    function ip_check($ip = null): void
    {
        $ip = is_null($ip) ? request()->ip() : $ip;
        $noAccess = get_sys_config('no_access_ip');
        $noAccess = !$noAccess ? [] : array_filter(explode("\n", str_replace("\r\n", "\n", $noAccess)));
        if ($noAccess && IpUtils::checkIp($ip, $noAccess)) {
            $response = Response::create(['msg' => 'No permission request'], 'json', 403);
            throw new HttpResponseException($response);
        }
    }
}

if (!function_exists('set_timezone')) {

    /**
     * 设置时区
     * @throws Throwable
     */
    function set_timezone($timezone = null): void
    {
        $defaultTimezone = Config::get('app.default_timezone');
        $timezone = is_null($timezone) ? get_sys_config('time_zone') : $timezone;
        if ($timezone && $defaultTimezone != $timezone) {
            Config::set([
                'app.default_timezone' => $timezone
            ]);
            date_default_timezone_set($timezone);
        }
    }
}

if (!function_exists('get_upload_config')) {

    /**
     * 获取上传配置
     * @return array
     */
    function get_upload_config(): array
    {
        // 存储/上传资料配置
        Event::trigger('uploadConfigInit', App::getInstance());

        $uploadConfig = Config::get('upload');
        $uploadConfig['max_size'] = Filesystem::fileUnitToByte($uploadConfig['max_size']);

        $upload = request()->upload;
        if (!$upload) {
            $uploadConfig['mode'] = 'local';
            return $uploadConfig;
        }
        unset($upload['cdn']);
        return array_merge($upload, $uploadConfig);
    }
}

if (!function_exists('get_auth_token')) {

    /**
     * 获取鉴权 token
     * @param array $names
     * @return string
     */
    function get_auth_token(array $names = ['ba', 'token']): string
    {
        $separators = [
            'header' => ['', '-'], // batoken、ba-token【ba_token 不在 header 的接受列表内因为兼容性不高，改用 http_ba_token】
            'param' => ['', '-', '_'], // batoken、ba-token、ba_token
            'server' => ['_'], // http_ba_token
        ];

        $tokens = [];
        $request = request();
        foreach ($separators as $fun => $sps) {
            foreach ($sps as $sp) {
                $tokens[] = $request->$fun(($fun == 'server' ? 'http_' : '') . implode($sp, $names));
            }
        }
        $tokens = array_filter($tokens);
        return array_values($tokens)[0] ?? '';
    }
}

if (!function_exists('keys_to_camel_case')) {

    /**
     * 将数组 key 的命名方式转换为小写驼峰
     * @param array $array 被转换的数组
     * @param array $keys 要转换的 key，默认所有
     * @return array
     */
    function keys_to_camel_case(array $array, array $keys = []): array
    {
        $result = [];
        foreach ($array as $key => $value) {
            // 将键名转换为驼峰命名
            $camelCaseKey = $keys && in_array($key, $keys) ? parse_name($key, 1, false) : $key;

            if (is_array($value)) {
                // 如果值是数组，递归转换
                $result[$camelCaseKey] = keys_to_camel_case($value);
            } else {
                $result[$camelCaseKey] = $value;
            }
        }
        return $result;
    }
}

if (!function_exists('verifyMd5Password')) {

    function verifyMd5Password(string $inputPassword, string $storedPassword, string $salt)
    {
        return createPassword($salt, $inputPassword) === $storedPassword;
    }
}

if (!function_exists('createRandomString')) {
    function createRandomString($prefix, $len = 6)
    {
        $randNum = Random::build('numeric', $len);
        return $prefix . date("md") . $randNum;
    }
}

if (!function_exists('createPasswordSalt')) {
    function createPasswordSalt()
    {
        $uuid = Random::uuid();
        return str_replace('-', '', $uuid);
    }
}

if (!function_exists('createPassword')) {
    function createPassword($salt, $password)
    {
        return md5($salt . $password);
    }
}

if (!function_exists('calculateAge')) {
    function calculateAge(string $birthday)
    {
        // PHP中计算年龄
        $birthDate = new DateTime($birthday);
        $currentDate = new DateTime();
        return $currentDate->diff($birthDate)->y;
    }
}

if (!function_exists('createAccountClassHourId')) {
    function createAccountClassHourId($len = 4)
    {
        $now = date("Ymd");
        $randNum = Random::build('numeric', $len);
        $code = $now . $randNum;
        $isExists = CoreAccountClassHourModel::where('id', $code)->count() > 0;
        if ($isExists) {
            $code = createAccountClassHourId($len);
        }
        return $code;
    }
}

if (!function_exists('isJson')) {
    function isJson($string)
    {
        json_decode($string);
        return json_last_error() === JSON_ERROR_NONE;
    }
}

if (!function_exists('get_query')) {
    function get_query(array $data)
    {
        $where = [];
        if (!array_key_exists('query', $data)) {
            return $where;
        }

        $query = $data['query'];
        if (!$query || !is_array($query)) {
            return $where;
        }
        foreach ($query as $key => $value) {
            $value = is_string($value) ? trim($value) : $value;
            if (in_array($value, ['""', "''"])) {
                $value = '';
            }
            // 分割查询类型和字段名（限制分割为两部分）
            $parts = explode('_', $key, 2);
            if (count($parts) !== 2) {
                continue; // 跳过格式不正确的键名
            }
            [$type, $field] = $parts;
            $field = lcfirst($field);
            switch ($type) {
                case 'EQ'://等于
                    $where[] = [$field, '=', $value];
                    break;
                case 'LT'://小于
                    $where[] = [$field, '<', $value];
                    break;
                case 'NE'://不等于
                    $where[] = [$field, '<>', $value];
                    break;
                case 'LIKE'://模糊查询
                    $where[] = [$field, 'like', "%{$value}%"];
                    break;
                case 'IN'://in查询
                    if (is_array($value)) {
                        $value = implode(',', $value);
                    }
                    $where[] = [$field, 'in', $value];
                    break;
                case 'BETWEEN'://between查询
                    if (is_array($value) && count($value) >= 2) {
                        // 检查是否是时间格式，如果是则转换为时间戳
                        if (is_datetime_string($value[0]) && is_datetime_string($value[1])) {
                            $value[0] = strtotime($value[0]);
                            $value[1] = strtotime($value[1]);
                        }
                        $where[] = [$field, 'between', $value];
                    }
                    break;
                case 'BETWEENDATE'://between查询
                    if (is_array($value) && count($value) >= 2) {
                        // 检查是否是时间格式，如果是则转换为时间戳
                        if (is_datetime_string($value[0]) && is_datetime_string($value[1])) {
                            $where[] = [$field, 'between', $value];
                        }
                    }
                    break;
            }
        }
        return $where;
    }
}
if (!function_exists('is_datetime_string')) {
    function is_datetime_string($str)
    {
        if (!is_string($str)) {
            return false;
        }
        // 匹配日期时间格式，如：2025-08-01 00:00:00 或 2025-08-01
        return preg_match('/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/', $str);
    }
}
if (!function_exists('get_page_params')) {
    function get_page_params(array $param): array
    {
        // 处理页码参数 (原get_var_page逻辑)
        $page = isset($param['index']) && $param['index'] > 0 ? $param['index'] : 1;

        // 处理每页条数参数 (原get_list_rows逻辑)
        $limit = isset($param['limit']) && $param['limit'] > 0 ? $param['limit'] : 999;

        return ['page' => (string)$page, 'limit' => (int)$limit];
    }
}

if (!function_exists('removeNull')) {
    function removeNull($array)
    {
        // 如果是对象，先转换为数组
        if (is_object($array)) {
            $array = $array->toArray();
        }

        // 确保输入是数组
        if (!is_array($array)) {
            return $array;
        }

        foreach ($array as $key => $value) {
            // 递归处理数组值
            if (is_array($value) || is_object($value)) {
                $array[$key] = removeNull($value);
            }

            // 移除null值
            if ($array[$key] === null) {
                unset($array[$key]);
            }
        }

        return $array;
    }
}


if (!function_exists("setString")) {

}

if (!function_exists('result_row')) {
    function result_row($data)
    {
        $data = $data->toArray();
        $rows = $data['data'] ?? [];
        $rows = array_map('removeNull', $rows);
        return [
            'total' => $data['total'],
            'current' => $data['current_page'],
            'pageSize' => $data['per_page'],
            'totalPage' => $data['last_page'],
            'rows' => $rows,
        ];
    }
}

if (!function_exists('checkIp')) {
    function checkIp($ip, $type = 'all')
    {
        if ($type === 'ipv4') {
            return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
        } elseif ($type === 'ipv6') {
            return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
        } else {
            return filter_var($ip, FILTER_VALIDATE_IP) !== false;
        }
    }
}

if (!function_exists('treeSelect')) {
    function secondaryTreeSelect($data, $idKey = 'id', $parentKey = 'parent_id', $childrenKey = 'children')
    {
        $tree = [];
        $references = [];

        $data = $data->toArray();
        // 第一遍遍历，建立ID映射关系
        foreach ($data as &$item) {
            if ($item[$parentKey] == 0) {
                $item[$childrenKey] = [];
                $references[$item[$idKey]] = &$item;
            }
        }

        foreach ($data as &$item) {
            if ($item[$parentKey] != 0) {
                $parentId = $item[$parentKey];
                if (isset($references[$parentId])) {
                    unset($item[$parentKey]);
                    unset($references[$parentId][$parentKey]);
                    $references[$parentId][$childrenKey][] = &$item;
                }
            } else {
                $tree[] = &$item;
            }
        }

        return $tree;
    }
}

if (!function_exists('getRealAddressByIp')) {
    function getRealAddressByIp($ip)
    {
        $ip2region = new \Ip2Region();
        $region = $ip2region->btreeSearch($ip);
        return $region['region'];
    }
}
if (!function_exists('secondToTime')) {
    /**
     * 秒数转为时间格式(mm:ss)
     *
     * @param int $seconds 需要转换的秒数
     * @return string 转换后的字符串
     * @throws InvalidArgumentException 如果秒数为负数
     */
    function secondToTime($seconds)
    {
        if ($seconds < 0 || !is_numeric($seconds) || !$seconds) {
            //            throw new InvalidArgumentException("Seconds must be a positive number!");
            return "00:00";
        }

        $minute = floor($seconds / 60);
        $second = $seconds % 60;

        return sprintf("%02d:%02d", $minute, $second);
    }
}
if (!function_exists('secondsToHours')) {
    /**
     * 将秒数转换为小时数
     * @param int $seconds 秒数
     * @param int $decimals 保留小数位数，默认为2位
     * @return float 转换后的小时数
     */
    function secondsToHours(int $seconds, int $decimals = 2): float
    {
        $hours = $seconds / 3600;
        return round($hours, $decimals);
    }
}
if (!function_exists('bcMath')) {
    /**
     * 高精度计算
     * @param $first
     * @param $second
     * @param string $type
     * @param int $pointNum
     * @return int|string|null
     */
    function bcMath($first, $second, string $type = '-', int $pointNum = 2)
    {
        $first = number_format($first, $pointNum, '.', '');
        $second = number_format($second, $pointNum, '.', '');
        switch ($type) {
            case '-':
                return bcsub($first, $second, $pointNum);
                break;
            case '+':
                return bcadd($first, $second, $pointNum);
                break;
            case '/':
                return bcdiv($first, $second, $pointNum);
                break;
            case '*':
                return bcmul($first, $second, $pointNum);
                break;
        }
        return 0;
    }
};

if (!function_exists('getResponseException')) {
    function getResponseException($msg, $data = null, $code = 1, $showLevel = 'SILENT')
    {
        $result = [
            'code' => $code,
            'msg' => $msg,
            'showLevel' => $showLevel,
            'success' => $code == 0,
        ];
        if ($result['success']) {
            $result['data'] = $data ? $data : false;
        }
        $response = Response::create($result, 'json', 200);
        throw new HttpResponseException($response);
    }
}

if (!function_exists('clearAll')) {
    function clearAll($data)
    {
        if (isset($data['createBy'])) {
            $data['createBy'] = null;
        }
        if (isset($data['createTime'])) {
            $data['createTime'] = null;
        }
        if (isset($data['updateBy'])) {
            $data['updateBy'] = null;
        }
        if (isset($data['updateTime'])) {
            $data['updateTime'] = null;
        }
        return $data;
    }
}

if (!function_exists('logSave')) {

    function logSave(array|string $msg, string $name = 'notice', string $file_name = 'logs'): bool
    {
        try {
            $save_path = root_path() . 'runtime/log_save/' . $file_name . '/' . date('Ymd') . '/';
            if (!file_exists($save_path)) {
                $rest = @mkdir($save_path, 0755, true);
                if (!$rest) return false;
            }
            // 删除三天前的文件夹
            $destination = $save_path . date('Ymd') . '-' . $name . '.log';
            $files = glob(root_path() . 'runtime/log_save/' . $file_name . '/*');
            if (count($files) > 2) {
                delDir($files[0]);
            }
            if (file_exists($destination) && filesize($destination) >= 50 * 1024 * 1024) {
                rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . date('Y-m-d_H:i:s') . '-' . basename($destination));
            }
            if (is_array($msg)) {
                $msg = print_r($msg, true);
            }
            @file_put_contents($destination, '执行时间:' . date('Y-m-d H:i:s') . PHP_EOL . '[' . $name . '] ' . $msg . PHP_EOL, FILE_APPEND);
            return true;
        } catch (\Exception $e) {
            return $e->getMessage();
        }
    }
}
if (!function_exists('delDir')) {
    function delDir($directory)
    {//自定义函数递归的函数整个目录
        if (file_exists($directory)) {//判断目录是否存在，如果不存在rmdir()函数会出错
            if ($dir_handle = @opendir($directory)) {//打开目录返回目录资源，并判断是否成功
                while ($filename = readdir($dir_handle)) {//遍历目录，读出目录中的文件或文件夹
                    if ($filename != '.' && $filename != '..') {//一定要排除两个特殊的目录
                        $subFile = $directory . "/" . $filename;//将目录下的文件与当前目录相连
                        if (is_dir($subFile)) {//如果是目录条件则成了
                            delDir($subFile);//递归调用自己删除子目录
                        }
                        if (is_file($subFile)) {//如果是文件条件则成立
                            unlink($subFile);//直接删除这个文件
                        }
                    }
                }
                closedir($dir_handle);//关闭目录资源
                rmdir($directory);//删除空目录
            }
        }
    }
}
if (!function_exists('createDirFile')) {
    /**
     * 创建目录并保存文件
     * @param string $savePath 保存路径
     * @param string $bytes 文件内容
     * @return bool|int 返回文件路径或false
     */
    function createDirFile(string $savePath, string $bytes = ''): bool|int
    {
        // 参数验证
        if (!$savePath || !$bytes) {
            return false;
        }

        try {
            $rootPath = app()->getRootPath();
            $filePath = pathinfo($savePath);

            // 验证路径信息完整性
            if (!isset($filePath['dirname']) || !isset($filePath['basename'])) {
                return false;
            }

            // 构建完整目录路径
            $saveDirPath = $rootPath . ltrim($filePath['dirname'], '/');

            // 确保目录存在
            if (!is_dir($saveDirPath)) {
                // 使用递归创建目录，设置适当的权限
                if (!mkdir($saveDirPath, 0755, true)) {
                    return false;
                }
            }

            // 构建完整文件路径
            $saveFilePath = $saveDirPath . DIRECTORY_SEPARATOR . $filePath['basename'];

            // 保存文件内容
            $result = file_put_contents($saveFilePath, $bytes);

            return $result !== false ? $saveFilePath : false;

        } catch (\Exception $e) {
            // 记录错误日志
            return false;
        }
    }

    if (!function_exists('normalizeAndCompare')) {
        function normalizeAndCompare($time)
        {
            // 标准化时间格式为 HH:MM:SS
            if (strlen($time) == 5) { // HH:MM 格式
                return $time . ':00';
            } elseif (strlen($time) == 8) { // HH:MM:SS 格式
                return $time;
            }
            return $time;
        }
    }

    if (!function_exists('buildNo')) {
        /**
         * 生成唯一的单号
         * @param string $prefix 前缀
         * @return string
         */
        function buildNo($prefix = 'E')
        {
            /* 选择一个随机的方案 */
            $yCode = [
                'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
                'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
            return $prefix .
                //            $yCode[intval(date('Y'))%34] .
                strtoupper(dechex(date('m'))) . date('d') . substr(time(), -5) .
                substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
        }
    }
}

if (!function_exists('urlencode_chinese_only_enhanced')) {
    //只对中文字符进行URL编码
    function urlencode_chinese_only_enhanced($str, $options = [])
    {
        // 默认选项
        $default_options = [
            'encode_slash' => false,  // 是否编码斜杠
        ];

        $options = array_merge($default_options, $options);

        // 匹配中文字符的正则表达式（优化版）
        // 覆盖更全面的中文字符范围，包括常用汉字、扩展A区、扩展B区等
        $pattern = '/[\x{4e00}-\x{9fff}\x{3400}-\x{4dbf}\x{20000}-\x{2a6df}\x{2a700}-\x{2b73f}\x{2b740}-\x{2b81f}\x{2b820}-\x{2ceaf}]+/u';

        // 使用urlencode对匹配到的中文字符进行编码
        return preg_replace_callback($pattern, function ($matches) {
            return urlencode($matches[0]);
        }, $str);
    }
}
